﻿/*
 * Copyright 2005-2011 Edward L. Stauff.                  Contact: <EdStauff@gmail.com>.
 * 
 * This file, "NiceXmlReader.cs", is part of the "Stauffware.Xml" library.
 * "Stauffware.Xml" is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser Public License as published by the 
 * Free Software Foundation, either version 3 of the License, or (at your 
 * option) any later version.
 * 
 * "Stauffware.Xml" is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser Public License 
 * for more details.
 * 
 * You should have received a copy of the GNU Lesser Public License along  
 * with "Stauffware.Xml".  If not, see <http://www.gnu.org/licenses/>.
 */

using System;
using System.Collections.Generic;
using System.Xml;
using System.Text;
using Stauffware.Common.Core;
using Stauffware.Common.Time;

namespace Stauffware.Common.Xml
{
	/// <summary>
	/// Specifies what to do with a particular XML node type.
	/// </summary>
	public enum XmlNodeAction
	{
		/// <summary> ignore the node (drop it on the floor) </summary>
		Ignore,		
		/// <summary> return the node for processing by the caller </summary>
		Return,		
		/// <summary> throw an exception </summary>
		Error,		
	};

	/// <summary>
	/// A subclass of XmlReader that wraps another XmlReader to provide additional capabilities.
	/// </summary>
	/// <remarks>
	/// Additional capabilities:
	///		Easily specify for each XML node type whether to ignore it, return it, 
	///			or throw an exception.
	///		Support for reporting non-fatal errors.
	///		Optional case insensitivity.
	///		Detailed error reporting, including the location of the error 
	///			in the XML document and (when available) the line number.
	///		Methods for checking XML syntax rules.
	///		Better methods for obtaining attribute values.
	/// </remarks>
	public class NiceXmlReader : XmlReader
	{
		/// <summary>
		/// Maps XML node types to actions.
		/// </summary>
		public class NodeActionMap
		{
			/// <summary>
			/// Gets or sets the action for the given node type.
			/// </summary>
			public XmlNodeAction this[XmlNodeType n]
			{
				get { return this.Dictionary[n]; }
				set { this.Dictionary[n] = value; }				
			}

			private Dictionary<XmlNodeType, XmlNodeAction> Dictionary = new Dictionary<XmlNodeType, XmlNodeAction>();

			internal NodeActionMap ()
			{
				foreach (XmlNodeType n in Enum.GetValues(typeof(XmlNodeType)))
					this.Dictionary[n] = XmlNodeAction.Return;
			}

			/// <summary>
			/// Sets all node types to the given action.
			/// </summary>
			/// <param name="a"></param>
			public void SetAll (XmlNodeAction a)
			{
				foreach (XmlNodeType n in this.Dictionary.Keys)
					this.Dictionary[n] = a;
			}
		}

		/// <summary>
		/// Specifies how the Read method should deal with each kind of XML node.
		/// </summary>
		public readonly NodeActionMap NodeActions = new NodeActionMap();

		/// <summary>
		/// This event is raised for each XML error that need not be fatal.
		/// If the event has no handlers, such errors will be raised as exceptions.
		/// </summary>
		public event XmlErrorReporter OnError;

		/// <summary>
		/// NiceXmlReader increments this property every time it reports an error.
		/// </summary>
		public int ErrorCount { get; set; }

		/// <summary>
		/// The maximum number of errors to report before aborting.
		/// The default value is 100.
		/// </summary>
		public int MaxErrors { get; set; }

		/// <summary>
		///Gets the current line number (if available from the inner XmlReader).
		/// </summary>
		public int LineNumber
		{
			get { return (this.XmlLineInfo == null) ? -1 : this.XmlLineInfo.LineNumber; }
		}

		/// <summary>
		/// Gets the current line position (if available from the inner XmlReader).
		/// </summary>
		public int LinePosition
		{
			get { return (this.XmlLineInfo == null) ? -1 : this.XmlLineInfo.LinePosition; }
		}

		/// <summary>
		/// Gets or sets whether to ignore the case of element and attribute names.
		/// The default is true (ignore case).
		/// </summary>
		public bool IgnoreCase { get; set; }

		/// <summary>
		/// Gets or sets whether to decode entities in attribute values as standard 
		/// HTML entities.  The default is true.
		/// </summary>
		public bool DecodeHtmlAttrEntities { get; set; }

		/// <summary> The inner XmlReader object. </summary>
		protected readonly XmlReader XmlReader;

		/// <summary> Our XmlReader cast to an IXmlLineInfo, or null. </summary>
		protected readonly IXmlLineInfo XmlLineInfo;

		/// <summary> Keeps track of where we are in the XML tree. </summary>
		protected readonly XmlElementContextStack ContextStack = new XmlElementContextStack();

		private static DateParser DateParser = new DateParser();
		private static TimeParser TimeParser = new TimeParser();
		private static DateTimeParser DateTimeParser = new DateTimeParser();

		#region Constructors

		//-----------------------------------------------------------------------------
		/// <summary>
		/// Constructor for use with a pre-existing XmlReader.
		/// </summary>
		/// <param name="rdr">the XmlReader to be wrapped by this NiceXmlReader</param>
		/// <param name="er">
		///		A delegate for reporting non-fatal errors.
		///		If null, then the first error encountered will throw an exception.
		/// </param>
		/// <remarks>
		/// All the other constructors call this one, directly or indirectly.
		/// </remarks>

		public NiceXmlReader (XmlReader rdr, XmlErrorReporter er)
		{
			Require.NotNull(rdr);
			this.XmlReader = rdr;

			this.XmlLineInfo = rdr as IXmlLineInfo;
			if (this.XmlLineInfo != null && !this.XmlLineInfo.HasLineInfo())
				this.XmlLineInfo = null;
	
			if (er != null)
				this.OnError += er;
			this.MaxErrors = 100;
			this.IgnoreCase = true;
			this.DecodeHtmlAttrEntities = true;
		}

		/// <summary>
		/// Constructor for use with a pre-existing XmlReader.
		/// </summary>
		/// <param name="rdr">the XmlReader to be wrapped by this NiceXmlReader</param>
		public NiceXmlReader (XmlReader rdr) : this(rdr, null) { }

		//-----------------------------------------------------------------------------

		/// <summary>
		/// Constructor for use with a URI (or file path).
		/// </summary>
		/// <param name="uri">where to read the XML from</param>
		/// <param name="s">reader settings, or null</param>
		/// <param name="er">
		///		A delegate for reporting non-fatal errors.
		///		If null, then the first error encountered will throw an exception.
		/// </param>
		public NiceXmlReader (string uri, XmlReaderSettings s, XmlErrorReporter er)
			: this(XmlReader.Create(uri, s), er) { }

		/// <summary>
		/// Constructor for use with a URI (or file path).
		/// </summary>
		/// <param name="uri">where to read the XML from</param>
		/// <param name="s">reader settings, or null</param>
		public NiceXmlReader (string uri, XmlReaderSettings s)
			: this(XmlReader.Create(uri, s)) { }

		/// <summary>
		/// Constructor for use with a URI (or file path).
		/// </summary>
		/// <param name="uri">where to read the XML from</param>
		/// <param name="er">
		///		A delegate for reporting non-fatal errors.
		///		If null, then the first error encountered will throw an exception.
		/// </param>
		public NiceXmlReader (string uri, XmlErrorReporter er)
			: this(XmlReader.Create(uri, CreateDefaultXmlReaderSettings()), er) { }

		/// <summary>
		/// Constructor for use with a URI (or file path).
		/// </summary>
		/// <param name="uri">where to read the XML from</param>
		public NiceXmlReader (string uri)
			: this(XmlReader.Create(uri, CreateDefaultXmlReaderSettings())) { }

		//-----------------------------------------------------------------------------
		/// <summary>
		/// Constructor for use with a TextReader.
		/// </summary>
		/// <param name="rdr">where to read the XML from</param>
		/// <param name="s">reader settings, or null</param>
		/// <param name="er">
		///		A delegate for reporting non-fatal errors.
		///		If null, then the first error encountered will throw an exception.
		/// </param>
		public NiceXmlReader (System.IO.TextReader rdr, XmlReaderSettings s, XmlErrorReporter er)
			: this(XmlReader.Create(rdr, s), er) { }

		/// <summary>
		/// Constructor for use with a TextReader.
		/// </summary>
		/// <param name="rdr">where to read the XML from</param>
		/// <param name="s">reader settings, or null</param>
		public NiceXmlReader (System.IO.TextReader rdr, XmlReaderSettings s)
			: this(XmlReader.Create(rdr, s)) { }

		/// <summary>
		/// Constructor for use with a TextReader.
		/// </summary>
		/// <param name="rdr">where to read the XML from</param>
		/// <param name="er">
		///		A delegate for reporting non-fatal errors.
		///		If null, then the first error encountered will throw an exception.
		/// </param>
		public NiceXmlReader (System.IO.TextReader rdr, XmlErrorReporter er)
			: this(XmlReader.Create(rdr, CreateDefaultXmlReaderSettings()), er) { }

		/// <summary>
		/// Constructor for use with a TextReader.
		/// </summary>
		public NiceXmlReader (System.IO.TextReader rdr)
			: this(XmlReader.Create(rdr, CreateDefaultXmlReaderSettings())) { }

		//-----------------------------------------------------------------------------
		/// <summary>
		/// Returns an XmlReaderSettings object, initialized with the defaults for
		/// NiceXmlReader.
		/// </summary>

		public static XmlReaderSettings CreateDefaultXmlReaderSettings ()
		{
			XmlReaderSettings xrs = new XmlReaderSettings();

			xrs.CheckCharacters = true;
			xrs.CloseInput = false;
			xrs.ConformanceLevel = ConformanceLevel.Auto;
			xrs.DtdProcessing = DtdProcessing.Prohibit;
			xrs.IgnoreComments = true;
			xrs.IgnoreProcessingInstructions = true;
			xrs.IgnoreWhitespace = true;
			xrs.Schemas = null;
			xrs.ValidationType = ValidationType.None;

			return xrs;
		}

		//-----------------------------------------------------------------------------

		#endregion

		#region Attribute Readers

		//-----------------------------------------------------------------------------
		/// <summary>
		/// Tries to gets the value of a string attribute, and return true on success
		/// or false if the attribute doesn't exist,.
		/// </summary>

		public bool GetStringAttribute (string name, ref string value)
		{
			string str = this.GetAttribute(name);
			if (str == null)
				return false;
			value = str;
			return true;
		}

		//-----------------------------------------------------------------------------
		/// <summary>
		/// Returns the value of the named attribute, or the given default value if
		/// the attribute doesn't exist.
		/// </summary>

		public string GetStringAttribute (string name, string defaultValue)
		{
			string str = this.GetAttribute(name);
			if (str == null)
				return defaultValue;
			return str;
		}

		//-----------------------------------------------------------------------------
		/// <summary>
		/// Tries to gets the value of a boolean attribute.  
		/// This method returns false if the attribute doesn't exist, otherwise it returns 
		/// true (unless it throws an exception).  If the value is invalid, this method 
		/// reports an error, then throws an exception if there is no OnError handler, 
		/// or if the handler returns false, or if the number of errors exceeds MaxErrors.
		/// </summary>

		public bool GetBooleanAttribute (string name, ref bool value)
		{
			string str = this.GetAttribute(name);
			if (str == null)
				return false;

			if (str == "yes" || str == "true" || str == "on")
				value = true;
			else if (str == "no" || str == "false" || str == "off")
				value = false;
			else
				this.BadXmlAttributeValue();

			return true;
		}

		//-----------------------------------------------------------------------------
		/// <summary>
		/// Tries to return the value of a boolean attribute.  
		/// If the attribute doesn't exist, it returns the given default value.
		/// If the value is invalid, it reports an error, then returns the given
		/// default value, or throws an exception if there is no OnError handler, 
		/// or if the handler returns false, or if the number of errors exceeds MaxErrors.
		/// </summary>

		public bool GetBooleanAttribute (string name, bool defaultValue)
		{
			bool retVal = defaultValue;
			this.GetBooleanAttribute(name, ref retVal);
			return retVal;
		}

		//-----------------------------------------------------------------------------
		/// <summary>
		/// Tries to gets the value of an integer attribute.  
		/// This method returns false if the attribute doesn't exist, otherwise it returns 
		/// true (unless it throws an exception).  If the value is invalid, this method 
		/// reports an error, then throws an exception if there is no OnError handler, 
		/// or if the handler returns false, or if the number of errors exceeds MaxErrors.
		/// </summary>

		public bool GetIntAttribute (string name, ref int value)
		{
			string str = this.GetAttribute(name);
			if (str == null)
				return false;

			int tempValue;
			if (!int.TryParse(str, out tempValue))
				this.BadXmlAttributeValue();
			else value = tempValue;
			return true;
		}

		//-----------------------------------------------------------------------------
		/// <summary>
		/// Tries to return the value of an integer attribute.  
		/// If the attribute doesn't exist, it returns the given default value.
		/// If the value is invalid, it reports an error, then returns the given
		/// default value, or throws an exception if there is no OnError handler, 
		/// or if the handler returns false, or if the number of errors exceeds MaxErrors.
		/// </summary>

		public int GetIntAttribute (string name, int defaultValue)
		{
			int retVal = defaultValue;
			this.GetIntAttribute(name, ref retVal);
			return retVal;
		}

		//-----------------------------------------------------------------------------
		/// <summary>
		/// Tries to gets the value of a decimal attribute.  
		/// This method returns false if the attribute doesn't exist, otherwise it returns 
		/// true (unless it throws an exception).  If the value is invalid, this method 
		/// reports an error, then throws an exception if there is no OnError handler, 
		/// or if the handler returns false, or if the number of errors exceeds MaxErrors.
		/// </summary>

		public bool GetDecimalAttribute (string name, ref decimal value)
		{
			string str = this.GetAttribute(name);
			if (str == null)
				return false;

			decimal tempValue;
			if (!decimal.TryParse(str, out tempValue))
				this.BadXmlAttributeValue();
			else value = tempValue;
			return true;
		}

		//-----------------------------------------------------------------------------
		/// <summary>
		/// Tries to return the value of a decimal attribute.  
		/// If the attribute doesn't exist, it returns the given default value.
		/// If the value is invalid, it reports an error, then returns the given
		/// default value, or throws an exception if there is no OnError handler, 
		/// or if the handler returns false, or if the number of errors exceeds MaxErrors.
		/// </summary>

		public decimal GetDecimalAttribute (string name, int defaultValue)
		{
			decimal retVal = defaultValue;
			this.GetDecimalAttribute(name, ref retVal);
			return retVal;
		}

		//-----------------------------------------------------------------------------
		/// <summary>
		/// Tries to gets the value of a float attribute.  
		/// This method returns false if the attribute doesn't exist, otherwise it returns 
		/// true (unless it throws an exception).  If the value is invalid, this method 
		/// reports an error, then throws an exception if there is no OnError handler, 
		/// or if the handler returns false, or if the number of errors exceeds MaxErrors.
		/// </summary>

		public bool GetFloatAttribute (string name, ref float value)
		{
			string str = this.GetAttribute(name);
			if (str == null)
				return false;

			float tempValue;
			if (!float.TryParse(str, out tempValue))
				this.BadXmlAttributeValue();

			value = tempValue;
			return true;
		}

		//-----------------------------------------------------------------------------
		/// <summary>
		/// Tries to return the value of a float attribute.  
		/// If the attribute doesn't exist, it returns the given default value.
		/// If the value is invalid, it reports an error, then returns the given
		/// default value, or throws an exception if there is no OnError handler, 
		/// or if the handler returns false, or if the number of errors exceeds MaxErrors.
		/// </summary>

		public float GetFloatAttribute (string name, int defaultValue)
		{
			float retVal = defaultValue;
			this.GetFloatAttribute(name, ref retVal);
			return retVal;
		}

		//-----------------------------------------------------------------------------
		/// <summary>
		/// Tries to gets the value of an enum attribute of type TYPE.  
		/// This method returns false if the attribute doesn't exist, otherwise it returns 
		/// true (unless it throws an exception).  If the value is invalid, this method 
		/// reports an error, then throws an exception if there is no OnError handler, 
		/// or if the handler returns false, or if the number of errors exceeds MaxErrors.
		/// </summary>

		public bool GetEnumAttribute<TYPE> (string name, ref TYPE value) where TYPE : struct
		{
			string str = this.GetAttribute(name);
			if (str == null)
				return false;
			TYPE tempValue;
			if (!(Enum.TryParse<TYPE>(str, true, out tempValue)))
				this.BadXmlAttributeValue();
			else value = tempValue;
			return true;
		}

		//-----------------------------------------------------------------------------
		/// <summary>
		/// Tries to gets the value of a DateTime attribute containing both a date and a time.  
		/// This method returns false if the attribute doesn't exist, otherwise it returns 
		/// true (unless it throws an exception).  If the value is invalid, this method 
		/// reports an error, then throws an exception if there is no OnError handler, 
		/// or if the handler returns false, or if the number of errors exceeds MaxErrors.
		/// </summary>

		public bool GetDateTimeAttribute (string name, ref DateTime value)
		{
			string str = this.GetAttribute(name);
			if (str == null)
				return false;
			DateTimeParse p = DateTimeParser.Parse(str);
			if (p.GotValue)
				value = p.Value;
			if (p.IsError)
				this.BadXmlAttributeValue(p.ErrorMessage);
			return true;
		}

		//-----------------------------------------------------------------------------
		/// <summary>
		/// Tries to gets the value of a DateTime attribute containing only a date.  
		/// This method returns false if the attribute doesn't exist, otherwise it returns 
		/// true (unless it throws an exception).  If the value is invalid, this method 
		/// reports an error, then throws an exception if there is no OnError handler, 
		/// or if the handler returns false, or if the number of errors exceeds MaxErrors.
		/// </summary>

		public bool GetDateAttribute (string name, ref DateTime value)
		{
			string str = this.GetAttribute(name);
			if (str == null)
				return false;
			DateParse p = DateParser.Parse(str);
			if (p.GotValue)
				value = p.Value;
			if (p.IsError)
				this.BadXmlAttributeValue(p.ErrorMessage);
			return true;
		}

		//-----------------------------------------------------------------------------
		/// <summary>
		/// Tries to gets the value of a DateTime attribute containing only a time.  
		/// This method returns false if the attribute doesn't exist, otherwise it returns 
		/// true (unless it throws an exception).  If the value is invalid, this method 
		/// reports an error, then throws an exception if there is no OnError handler, 
		/// or if the handler returns false, or if the number of errors exceeds MaxErrors.
		/// </summary>

		public bool GetTimeAttribute (string name, ref DateTime value)
		{
			string str = this.GetAttribute(name);
			if (str == null)
				return false;
			TimeParse p = TimeParser.Parse(str);
			if (p.GotValue)
				value = p.Value;
			if (p.IsError)
				this.BadXmlAttributeValue(p.ErrorMessage);
			return true;
		}

		//-----------------------------------------------------------------------------

		#endregion

		#region Xml Rules

		/// <summary>
		/// Returns whether the given node type can have a value.
		/// </summary>
		public static bool CanHaveValue (XmlNodeType type)
		{
			return (type == XmlNodeType.Attribute) 
				|| (type == XmlNodeType.CDATA)
				|| (type == XmlNodeType.Comment) 
				|| (type == XmlNodeType.DocumentType) 
				|| (type == XmlNodeType.ProcessingInstruction) 
				|| (type == XmlNodeType.Text)
				|| (type == XmlNodeType.SignificantWhitespace)
				|| (type == XmlNodeType.Whitespace)
				|| (type == XmlNodeType.XmlDeclaration);
		}

		/// <summary>
		/// Returns whether the given node type can have a name.
		/// (The Name property also returns a name for EndElement.)
		/// </summary>
		public static bool CanHaveName (XmlNodeType type)
		{
			return (type == XmlNodeType.Attribute) 
				|| (type == XmlNodeType.DocumentType)
				|| (type == XmlNodeType.Element) 
				|| (type == XmlNodeType.EntityReference) 
				|| (type == XmlNodeType.ProcessingInstruction)
				|| (type == XmlNodeType.XmlDeclaration);
		}

		/// <summary>
		/// Returns whether the current node type *CAN* have a name,
		/// regardless of whether or not it actually does.
		/// </summary>
		public bool HasName 
		{
			get { return CanHaveName(this.NodeType); }
		}

		#endregion

		#region Error Checking & Reporting

		//-----------------------------------------------------------------------------
		/// <summary>
		/// Reports an error if the current node is not of the given type.
		/// </summary>

		public void Expect (XmlNodeType type)
		{
			if (this.NodeType != type)
				this._onError(new XmlError(this, "expecting an XML " + type.ToString() + 
										"\", but found an XML " + this.CurrentNodeToString()));
		}

		//-----------------------------------------------------------------------------
		/// <summary>
		/// Reports an error if the current node is not a Element with the
		/// given name.
		/// </summary>

		public void ExpectElement (string name)
		{
			if ((this.NodeType != XmlNodeType.Element) || 
				(0 != string.Compare(this.Name, name, this.IgnoreCase)))
				this._onError(new XmlError(this, "expecting XML element \"" + name + 
										"\", but found " + this.CurrentNodeToString()));
		}

		//-----------------------------------------------------------------------------
		/// <summary>
		/// Reports an error if the current node is not an EndElement with the
		/// given name.
		/// </summary>

		public void ExpectEndElement (string name)
		{
			if ((this.NodeType != XmlNodeType.EndElement) || 
				(0 != string.Compare(this.Name, name, this.IgnoreCase)))
				this._onError(new XmlError(this, "expecting XML EndElement \"" + name + 
										"\", but found " + this.CurrentNodeToString()));
		}

		//-----------------------------------------------------------------------------

		/// <summary>
		/// Reports that the current attribute name is invalid.
		/// </summary>
		public void BadXmlAttributeName ()
		{
			Require.Condition(this.NodeType == System.Xml.XmlNodeType.Attribute);
			this._onError(new XmlError(this, "\"" + this.Name + 
										"\" is not a valid attribute name for <" + 
										this.ContextStack.Top.Name + ">"));
		}

		/// <summary>
		/// Reports that the current attribute value is invalid.
		/// </summary>
		public void BadXmlAttributeValue ( )
		{
			Require.Condition(this.NodeType == System.Xml.XmlNodeType.Attribute);
			this._onError(new XmlError(this, "\"" + this.Value + 
							"\" is not a valid value for attribute \"" + this.Name + "\""));
		}

		/// <summary>
		/// Reports that the current attribute value is invalid.
		/// </summary>
		public void BadXmlAttributeValue (string description)
		{
			this._onError(new XmlError(this, "attribute \"" + this.Name + "\" - " + description));
		}

		/// <summary>
		/// Reports that a required attribute is missing.
		/// </summary>
		public void MissingAttribute (string attrName)
		{
			this._onError(new XmlError(this, "missing required attribute: \"" + attrName));
		}

		/// <summary>
		/// Reports that the current element isn't a valid child of its parent.
		/// </summary>
		public void BadXmlChildElement ( )
		{
			this._onError(new XmlError(this, "<" + this.ContextStack.Top.Name + 
										"> is not a valid child element for <" + 
										this.ContextStack.GetFromTop(1).Name + ">"));
		}

		///// <summary>
		///// Reports that the content of the current element isn't valid.
		///// </summary>
		//public void BadXmlContent ( )
		//{
		//    this._onError(new XmlError(this, "invalid content for this element <" + this.Name + ">"));
		//}

		/// <summary>
		/// Reports a generic XML error.
		/// </summary>
		public void BadXml (string message)
		{
			this._onError(new XmlError(this, message));
		}

		//-----------------------------------------------------------------------------
		/// <summary>
		/// Returns a description of the current context (location) in the XML document.
		/// </summary>

		public string GetElementContext ()
		{
			return this.ContextStack.ToString(" | ");
		}

		//-----------------------------------------------------------------------------
		/// <summary> Reports an error.  May or may not return. </summary>

		private void _onError (XmlError err)
		{
			this.ErrorCount++;

			if (this.OnError == null)
				throw err;
			if (!this.OnError(err))
				throw err;
			if (this.ErrorCount > this.MaxErrors)
			    throw new XmlError(this, "giving up after too many errors (" + 
													this.ErrorCount.ToString() + ")");

			//if (++this.ErrorCount > this.MaxErrors)
			//    throw new XmlError(this, "giving up after too many errors (" + 
			//                                        this.ErrorCount.ToString() + ")");
			//if (this.OnError != null)
			//    if (this.OnError(err))
			//        return;
			//throw err;
		}

		//-----------------------------------------------------------------------------
		/// <summary>
		/// Returns a string representation of the current node, for error reporting.
		/// </summary>

		public string CurrentNodeToString ()
		{
			bool hasName = this.HasName || (this.NodeType == XmlNodeType.EndElement);

			if (hasName && this.HasValue)
				return this.NodeType.ToString() + " \"" + this.Name + "\" = \"" + this.Value + "\"";
			if (hasName)
				return this.NodeType.ToString() + " \"" + this.Name + "\"";
			if (this.HasValue)
				return this.NodeType.ToString() + " \"" + this.Value + "\"";

			return this.NodeType.ToString();
		}

		//-----------------------------------------------------------------------------

		#endregion

		#region XmlReader method wrappers that modify the behaviour 

		//-----------------------------------------------------------------------------

		/// <inherit/>
		public override bool Read ( )
		{
			while (this.XmlReader.Read())
			{
				// maintain element context stack (for error reporting)

				while ((!this.ContextStack.IsEmpty) && (this.Depth <= this.ContextStack.Top.NodeDepth))
					this.ContextStack.Pop();

				if (this.NodeType == XmlNodeType.Element)
					this.ContextStack.Push(this);

				// process the node

				switch (this.NodeActions[this.NodeType])
				{
					case XmlNodeAction.Error:
						this._onError(new XmlError(this, "invalid XML node type: " + this.NodeType.ToString()));
						break;

					case XmlNodeAction.Ignore:
						break;

					case XmlNodeAction.Return:
						return true;

					default:
						Assert.NotReached("bogus action: " + this.NodeActions[this.NodeType].ToString());
						break;
				}
			}

			return false;
		}

		//-----------------------------------------------------------------------------

		/// <summary>
		/// Returns an attribute value, automatically decoding HTML entities.
		/// </summary>
		public override string GetAttribute (int i)
		{
			if (this.DecodeHtmlAttrEntities)
				return CharEncoding.EntitiesToUnicode(this.XmlReader.GetAttribute(i));
			else return this.XmlReader.GetAttribute(i);
		}

		/// <summary>
		/// Returns an attribute value, automatically decoding HTML entities.
		/// </summary>
		public override string GetAttribute (string name)
		{
			string value = this.XmlReader.GetAttribute(name);
			if (this.DecodeHtmlAttrEntities && value != null)
				return CharEncoding.EntitiesToUnicode(value);
			else return value;
		}

		/// <summary>
		/// Returns an attribute value, automatically decoding HTML entities.
		/// </summary>
		public override string GetAttribute (string name, string namespaceURI)
		{
			if (this.DecodeHtmlAttrEntities)
				return CharEncoding.EntitiesToUnicode(this.XmlReader.GetAttribute(name, namespaceURI));
			else return this.XmlReader.GetAttribute(name, namespaceURI);
		}

		//-----------------------------------------------------------------------------
		
		#endregion

		#region transparent XmlReader method wrappers

		/// <inherit/>
		public override int AttributeCount
		{
			get { return this.XmlReader.AttributeCount; }
		}

		/// <inherit/>
		public override string BaseURI
		{
			get { return this.XmlReader.BaseURI; }
		}

		/// <inherit/>
		public override void Close ()
		{
			this.XmlReader.Close();
		}

		/// <inherit/>
		public override int Depth
		{
			get { return this.XmlReader.Depth; }
		}

		/// <inherit/>
		public override bool EOF
		{
			get { return this.XmlReader.EOF; }
		}

		/// <inherit/>
		public override bool IsEmptyElement
		{
			get { return this.XmlReader.IsEmptyElement; }
		}

		/// <inherit/>
		public override string LocalName
		{
			get { return this.XmlReader.LocalName; }
		}

		/// <inherit/>
		public override string LookupNamespace (string prefix)
		{
			return this.XmlReader.LookupNamespace(prefix);
		}

		/// <inherit/>
		public override bool MoveToAttribute (string name, string ns)
		{
			return this.XmlReader.MoveToAttribute(name, ns);
		}

		/// <inherit/>
		public override bool MoveToAttribute (string name)
		{
			return this.XmlReader.MoveToAttribute(name);
		}

		/// <inherit/>
		public override bool MoveToElement ()
		{
			return this.XmlReader.MoveToElement();
		}

		/// <inherit/>
		public override bool MoveToFirstAttribute ()
		{
			return this.XmlReader.MoveToFirstAttribute();
		}

		/// <inherit/>
		public override bool MoveToNextAttribute ()
		{
			return this.XmlReader.MoveToNextAttribute();
		}

		/// <inherit/>
		public override XmlNameTable NameTable
		{
			get { return this.XmlReader.NameTable; }
		}

		/// <inherit/>
		public override string NamespaceURI
		{
			get { return this.XmlReader.NamespaceURI; }
		}

		/// <inherit/>
		public override XmlNodeType NodeType
		{
			get { return this.XmlReader.NodeType; }
		}

		/// <inherit/>
		public override string Prefix
		{
			get { return this.XmlReader.Prefix; }
		}

		/// <inherit/>
		public override bool ReadAttributeValue ()
		{
			return this.XmlReader.ReadAttributeValue();
		}

		/// <inherit/>
		public override ReadState ReadState
		{
			get { return this.XmlReader.ReadState; }
		}

		/// <inherit/>
		public override void ResolveEntity ()
		{
			this.XmlReader.ResolveEntity();
		}

		/// <inherit/>
		public override string Value
		{
			get { return this.XmlReader.Value; }
		}

		#endregion
	}

}
